package solver

import (
	"fmt"
	"log"
	"slices"
	"strings"
)

/*
There are 3 kinds of finned X-Wing:

 1. Two lines are like
    ..XX..X..
    ...X..X..

 2. Two lines are like
    .XXX..X..
    ...X..X..

 3. Two lines are like
    .XX...X..
    ...X..X..

Note:

	..X...X..
	...X..X..

We don't consider this situation since it's considered as the Strategy *Skyscraper*.
*/

func (g *Grid) FinnedXWing() (used bool) {
	for cand := 0; cand < 9; cand++ {
		if g.finnedXWingLines(cand, g.rows) {
			used = true
		}
	}

	for cand := 0; cand < 9; cand++ {
		if g.finnedXWingLines(cand, g.cols) {
			used = true
		}
	}
	return
}

func sameBox(cellNums []int) bool {
	box := cellNums[0] / 3
	for _, cellNum := range cellNums[1:] {
		if cellNum/3 != box {
			return false
		}
	}
	return true
}

func finXWingPattern1(finNums, normalNums []int) bool {
	if len(finNums) != 3 || len(normalNums) != 2 || sameBox(normalNums) {
		return false
	}

	if !slices.Equal(intersect(finNums, normalNums), normalNums) {
		return false
	}

	if !slices.Equal(union(finNums, normalNums), finNums) {
		return false
	}

	if !sameBox(finNums[:2]) && !sameBox(finNums[1:]) {
		return false
	}

	return true
}

func finXWingPattern2(finNums, normalNums []int) bool {
	if len(finNums) != 4 || len(normalNums) != 2 || sameBox(normalNums) {
		return false
	}

	if !slices.Equal(intersect(finNums, normalNums), normalNums) {
		return false
	}

	if !slices.Equal(union(finNums, normalNums), finNums) {
		return false
	}

	if !sameBox(finNums[:3]) && !sameBox(finNums[1:]) {
		return false
	}

	return true
}

func finXWingPattern3(finNums, normalNums []int) bool {
	if len(finNums) != 3 || len(normalNums) != 2 || sameBox(normalNums) {
		return false
	}

	is := intersect(finNums, normalNums)
	us := union(finNums, normalNums)
	if len(is) != 1 || len(us) != 4 {
		return false
	}

	if is[0] == normalNums[0] && sameBox(us[1:]) {
		return true
	}

	if is[0] == normalNums[1] && sameBox(us[:3]) {
		return true
	}

	return false
}

func (g *Grid) finnedXWingLines(cand int, lines [9]House) (used bool) {

	for i := 0; i < 8; i++ {
		cellNums1 := intsFromBools(boolCellNums(lines[i], cand))
		if len(cellNums1) < 2 || len(cellNums1) > 4 {
			continue
		}
		for j := i + 1; j < 9; j++ {
			cellNums2 := intsFromBools(boolCellNums(lines[j], cand))
			if len(cellNums2) < 2 || len(cellNums2) > 4 {
				continue
			}
			var finNums, normalNums []int
			var finLine, normalLine House
			if len(cellNums2) == 2 {
				finNums = cellNums1
				normalNums = cellNums2
				finLine = lines[i]
				normalLine = lines[j]
			} else if len(cellNums1) == 2 {
				finNums = cellNums2
				normalNums = cellNums1
				finLine = lines[j]
				normalLine = lines[i]
			} else {
				continue
			}
			//if sameBox(normalNums) {
			//	continue
			//}

			if finXWingPattern1(finNums, normalNums) || finXWingPattern2(
				finNums, normalNums) || finXWingPattern3(finNums, normalNums) {
				if g.removeFinnedXWing(cand, finLine, normalLine, finNums, normalNums) {
					finnedFishLog(cand, finLine, normalLine, finNums, normalNums)
					used = true
				}
			}
		}
	}
	return
}

func finnedFishLog(cand int, finLine, normalLine House, finNums, normalNums []int) {
	var b strings.Builder
	var firstHouse, secondHouse string
	var lineNums []int
	var finLineNum int
	if finLine.HouseType() == 0 { //row
		firstHouse = "r"
		secondHouse = "c"
		lineNums = append(lineNums, finLine.cells[0].rowNum, normalLine.cells[0].rowNum)
		finLineNum = finLine.cells[0].rowNum
	} else {
		firstHouse = "c"
		secondHouse = "r"
		lineNums = append(lineNums, finLine.cells[0].colNum, normalLine.cells[0].colNum)
		finLineNum = finLine.cells[0].colNum
	}

	fmt.Fprint(&b, firstHouse)
	for _, num := range lineNums {
		fmt.Fprint(&b, num)
	}
	fmt.Fprint(&b, secondHouse)
	for _, num := range normalNums {
		fmt.Fprint(&b, num)
	}
	fmt.Fprintf(&b, " f%v%v%v", firstHouse, finLineNum, secondHouse)
	for _, num := range difference(finNums, normalNums) {
		fmt.Fprint(&b, num)
	}

	fmt.Fprintf(&b, " %v ", cand+1)
	switch len(normalNums) {
	case 2:
		fmt.Fprint(&b, "Finned XWing")
	case 3:
		fmt.Fprint(&b, "Finned Swordfish")
	case 4:
		fmt.Fprint(&b, "Finned Jellyfish")
	}

	log.Println(b.String())
}

func (g *Grid) removeFinnedXWing(cand int, finLine, normalLine House, finNums, normalNums []int) (used bool) {
	fin := difference(finNums, normalNums)[0]
	var removeNum int
	if sameBox([]int{fin, normalNums[0]}) {
		removeNum = normalNums[0]
	}
	if sameBox([]int{fin, normalNums[1]}) {
		removeNum = normalNums[1]
	}

	cells := g.boxes[finLine.cells[fin].boxNum].cells[:]
	if finLine.HouseType() == 0 {
		cells = shareCells(cells, g.cols[removeNum].cells[:])
	} else {
		cells = shareCells(cells, g.rows[removeNum].cells[:])
	}

	for _, cell := range cells {
		if cell.solved() || !cell.cands[cand] || equalCells(
			cell, normalLine.cells[removeNum]) || equalCells(
			cell, finLine.cells[removeNum]) {
			continue
		}
		if cell.removeCand(cand) {
			used = true
		}
	}
	return
}

func boolCellNums(line House, cand int) [9]bool {
	nums := [9]bool{}
	for i, cell := range line.cells {
		nums[i] = cell.cands[cand]
	}
	return nums
}

/*
func (g *Grid) finnedXWingLines(lines [9]House) (used bool) {
	for _, lineNums := range combine(
		[]int{0, 1, 2, 3, 4, 5, 6, 7, 8}, 2) {
		for cand := 0; cand < 9; cand++ {
			if g.handleFinnedXWing(cand, lines, lineNums) {
				return true
			}
		}
	}

	return
}



func (g *Grid) handleFinnedXWing(cand int, lines [9]House, lineNums []int) (used bool) {
	cellCount := len(lineNums)
	if cellCount != 2 {
		return false
	}
	normalLineNum := 0
	finLineNum := 1
	normalCellNums := intsFromBools(boolCellNums(lines[lineNums[0]], cand))
	finCellNums := intsFromBools(boolCellNums(lines[lineNums[1]], cand))

	if len(normalCellNums) != 2 {
		normalLineNum, finLineNum = finLineNum, normalLineNum
		normalCellNums, finCellNums = finCellNums, normalCellNums
	}

	if len(normalCellNums) != 2 {
		return false
	}
	if len(finCellNums) < 3 || len(finCellNums) > 4 {
		return false
	}

	totalCellNums := union(normalCellNums, finCellNums)

	if len(totalCellNums) > 4 {
		return false
	}

	if !finInBox(finCellNums, cellCount) {
		return false
	}

	if clearupFishLines(lines, cand, lineNums, cellNums) {
		used = true
		fishLog(lines, cand, lineNums, cellNums)
	}
	return
}

func finInBox(nums []int, cellCount int) bool {
	size := len(nums)
	finCount := size - cellCount
	inBox := false
	if finCount == 1 {
		inBox = (nums[0]/3 == nums[1]/3) || (nums[size-2]/3 == nums[size-1]/3)
	} else {
		inBox = (nums[0]/3 == nums[1]/3 && nums[0]/3 == nums[1]/3)
		inBox = inBox || (nums[size-3]/3 == nums[size-2]/3 && nums[size-2]/3 == nums[size-1]/3)
	}
	return inBox
}

/*
func verifyFinnedFishLine(lines [9]House, cand int, lineNums []int) (cellNums []int) {
	candCells := [9]bool{}
	for _, lineNum := range lineNums {
		for j := 0; j < 9; j++ {
			candCells[j] = candCells[j] || lines[lineNum].cells[j].cands[cand]
		}
	}
	cellNums = intsFromBools(candCells)
	return
}
*/
